package hh.util.bean;

import java.beans.*;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Collectors;

public class BeanUtil {

    public static void main(String[] args) throws Exception{
        ChannelDTO channelDTO = new ChannelDTO();
        ChannelDO channelDO = new ChannelDO();
        channelDO.setName("jd");
        channelDO.setCode("ddd");
        channelDO.setStatus(123);
        channelDO.setIsOpenSelf(true);

        channelDO.setId(1234567890l);


        BeanInfo beanInfo = Introspector.getBeanInfo(ChannelDTO.class);

        Map<String, Object> map = beanToMap(channelDO);
        System.out.println("beanToMap=" + map);


        System.out.println("mapToBean=" + mapToBean(map, ChannelDO.class));


    }

    public static final Map<Class<?>, Map<String, PropertyDescriptor>> classToPropDesc = new HashMap<>(1024);
    public static final Map<Class<?>, ClassInfo> beanInfoMap = new HashMap<>(1024);


    @Deprecated
    public static <T> Map<String, PropertyDescriptor> getPropertyDesc(Class<T> type) {
        Map<String, PropertyDescriptor> v = classToPropDesc.get(type);
        // 不需要考虑并发，并发也没关系
        if (v == null) {
            try {
                classToPropDesc.put(type, v = Arrays.stream(Introspector.getBeanInfo(type, Object.class).getPropertyDescriptors())
                        .collect(Collectors.toMap(FeatureDescriptor::getName, e -> e)));
            } catch (IntrospectionException e) {
                throw new RuntimeException(e);
            }
        }
        return v;
    }

    public static <T> T copy(Object source, Class<T> dest) {
        if (source == null || dest == null) return null;
        Object target = newInstance(dest);

        ClassInfo sourceClassInfo = getJavaClassInfo(source.getClass());
        ClassInfo destJavaClassInfo = getJavaClassInfo(dest);

        while (sourceClassInfo != null) {
            for (ReflectFieldInfo sourceFieldInfo : sourceClassInfo.getFieldInfos()) {
                Object value = sourceFieldInfo.read(source);
                if (value == null) continue;
                ReflectFieldInfo destFieldInfo = destJavaClassInfo.getFieldInfo(sourceFieldInfo.getFieldName());
                if (destFieldInfo == null) continue;
                destFieldInfo.write(target, value);
            }
            sourceClassInfo = sourceClassInfo.getParent();
        }
        return (T) target;
    }

    public static <T> T copy(Object source, T dest) {
        //        return  (dest instanceof Class) ? copy(source, (Class<T>) dest) : copy(source, dest, true);
        return copy(source, dest, true);
    }

    public static <T> T copy(Object source, T dest, boolean ignoreNull) {
        if (source == null || dest == null) return null;

        ClassInfo sourceClassInfo = getJavaClassInfo(source.getClass());
        ClassInfo destJavaClassInfo = getJavaClassInfo(dest.getClass());

        while (sourceClassInfo != null) {
            for (ReflectFieldInfo sourceFieldInfo : sourceClassInfo.getFieldInfos()) {

                Object value = sourceFieldInfo.read(source);
                if (value == null && ignoreNull) continue;
                ReflectFieldInfo destFieldInfo = destJavaClassInfo.getFieldInfo(sourceFieldInfo.getFieldName());
                if (destFieldInfo == null) continue;
                destFieldInfo.write(dest, value);
            }
            sourceClassInfo = sourceClassInfo.getParent();
        }

        return dest;
    }

    public static <T> Object newInstance(Class<T> dest) {
        try {
            return dest.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static <T> T mapToBean(Map<?, ?> map, Class<T> type) {
        return mapToBean(map, type, true);
    }

    public static Map<String, Object> beanToMap(Object bean) {
        return beanToMap(bean, null, true);
    }

    public static Map<String, Object> beanToMap(Object bean, Map map) {
        return beanToMap(bean, map, true);
    }

    private static Map<String, Object> beanToMap(Object bean, Map map, boolean ignoreNull) {
        if (bean == null) return null;
        if (map == null) map = new HashMap<>(16);

        ClassInfo javaClassInfo = getJavaClassInfo(bean.getClass());
        while (javaClassInfo != null) {
            for (ReflectFieldInfo fieldInfo : javaClassInfo.getFieldInfos()) {
                Object value = fieldInfo.read(bean);
                if (value == null && ignoreNull) continue;
                map.put(fieldInfo.getFieldName(), value);
            }
            javaClassInfo = javaClassInfo.getParent();
        }
        return map;
    }

    public static <T> T mapToBean(Map<?, ?> map, Class<T> type, boolean ignoreNull) {
        T t = null;
        try {
            t = type.newInstance();
            ClassInfo javaClassInfo = getJavaClassInfo(type);
            for (Map.Entry<?, ?> entry : map.entrySet()) {
                ReflectFieldInfo fieldInfo = javaClassInfo.getFieldInfo((String) entry.getKey());
                if (fieldInfo != null) {
                    if (ignoreNull && entry.getValue() == null) continue;
                    fieldInfo.write(t, entry.getValue());
                }
            }
            return t;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }



    public static ClassInfo getJavaClassInfo(Class<?> type) {
        return getJavaClassInfo(type, null);
    }

    public static ClassInfo getJavaClassInfo(Class<?> type, Class parent) {
        if (parent == null) parent = Object.class;
        ClassInfo rs = doGetClassInfo(type);
        ClassInfo beanInfo = rs;

        while (true) {
            type = type.getSuperclass();
            if (type != parent) {
                beanInfo.setParent(doGetClassInfo(type));
                beanInfo = beanInfo.getParent();
            } else break;
        }
        return rs;
    }

    public static<T> ClassInfo<T> doGetClassInfo(Class<T> type) {
        ClassInfo beanInfo = beanInfoMap.get(type);
        if (beanInfo != null) return beanInfo;

        synchronized (beanInfoMap) {
            if ((beanInfo = beanInfoMap.get(type)) != null) return beanInfo;
            beanInfo = new ClassInfo<>(type);
            Field[] declaredFields = type.getDeclaredFields();
            for (Field field : declaredFields) {
                int modifiers = field.getModifiers();
                if (Modifier.isAbstract(modifiers) || Modifier.isStatic(modifiers)) {
                    continue;
                }

                ReflectFieldInfo fieldInfo = new ReflectFieldInfo(field,ClassUtil.findGetMethod(field),ClassUtil.findSetMethod(field));

//                beanInfo.addFieldInfo(fieldInfo);
            }
            beanInfoMap.put(type, beanInfo);

        }
        return beanInfo;
    }

}
